//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using System;
using System.Collections.Generic;

namespace Microsoft.Research.ScientificWorkflow.Helper
{
    /// <summary>
    /// This class is used to hold the internal details of the DAP types.
    /// For instance DAP supports scalar types which can be mapped to the
    /// simple .NET types.
    /// But the more complicated types like Grid, Structure etc have an
    /// internal structure. This class is for storing the internal structure
    /// for the corresponding DAP type. This class should be able to hold 
    /// metadata for the simpler types like scalar as well as the more complex
    /// types like Grids.
    /// </summary>
    public class InternalDAPType
    {
        /// <summary>
        /// Constructor to store layout of the DAP type Scalar or Array.
        /// </summary>
        internal InternalDAPType(string variableName, OpenDAPURI.DapVariableType dapType, string XDRType, string name,
            int length)
        {
            if (OpenDAPURI.DapVariableType.Float64 != dapType &&
                OpenDAPURI.DapVariableType.Int16 != dapType &&
                OpenDAPURI.DapVariableType.Int32 != dapType &&
                OpenDAPURI.DapVariableType.Float32 != dapType)
                throw new NotSupportedException("Wrong constructor for the DAP type");

            this.varName = variableName;
            this.dapType = dapType;
            this.XDRType = new string[] { XDRType };
            this.ArrayNames = new string[] { name };
            this.ArrayLengths = new int[] { length };
        }


        /// <summary>
        /// Constructor to store layout of the DAP type Grid.
        /// </summary>
        internal InternalDAPType(string variableName, OpenDAPURI.DapVariableType dapType, string[] XDRTypes,
            string[] names, int[] lengths)
        {
            if (OpenDAPURI.DapVariableType.Array != dapType &&
                OpenDAPURI.DapVariableType.Grid != dapType)
                throw new NotSupportedException("Wrong constructor for the DAP type");

            this.varName = variableName;
            this.dapType = dapType;
            this.XDRType = XDRTypes;
            this.ArrayNames = names;
            this.ArrayLengths = lengths;
        }

        #region Public Properties

        /// <summary>
        /// DAP Type of the variable
        /// </summary>
        public OpenDAPURI.DapVariableType DapType
        {
            get { return this.dapType; }
        }

        /// <summary>
        /// XDR Type array
        /// </summary>
        public string[] XDRTypes
        {
            get { return this.XDRType; }
        }


        /// <summary>
        /// XDR Type array
        /// </summary>
        public string[] VariableNames
        {
            get { return this.ArrayNames; }
        }

        /// <summary>
        /// Array Lengths
        /// </summary>
        public int[] VariableLengths
        {
            get { return this.ArrayLengths; }
        }

        /// <summary>
        /// Primary name of the variable for container types.
        /// </summary>
        public string MainName
        {
            get { return this.varName; }
        }

        #endregion
        private string varName;
        private OpenDAPURI.DapVariableType dapType;
        private string[] XDRType;
        private string[] ArrayNames;
        private int[] ArrayLengths;
    }

    /// <summary>
    /// Class for holding the selection details.
    /// </summary>
    public class Selection
    {
        public Selection(OpenDAPURI.DAPOperators selection, string[] values)
        {
            this.selection = selection;
            this.values = values;
        }

        /// <summary>
        /// Selection Type
        /// </summary>
        public OpenDAPURI.DAPOperators selection;

        /// <summary>
        /// List of values to be used for the selection.
        /// </summary>
        public string[] values;
    }

    /// <summary>
    /// This class represent a URI for querying the OpenDAP 
    /// server. The OpenDAP URI is made up of several parameters.
    /// This class exposes those as properties which saves the
    /// effort of having to parse the URI every time access to
    /// a particular parameter is desired.
    /// </summary>
    public class OpenDAPURI
    {
        /// <summary>
        /// Enum for variable types to be retrieved
        /// </summary>
        public enum DapVariableType
        {
            Int32,
            Int16,
            Float32,
            Float64,
            Array,
            Grid
        }

        /// <summary>
        /// Enum for the operators supported by the open DAP query 
        /// </summary>
        public enum DAPOperators
        {
            /// <summary>
            /// Operator for sub sampling compound types. Follows the syntax
            /// variable[start:stride:stop].
            /// Applies to compound varaible types
            /// </summary>
            Sample,
        }

        /// <summary>
        /// An Open Dap URI has the following components
        /// a. server name http://dods.gso.uri.edu
        /// b. file name cgi-bin/nph-nc/data/fnoc1.nc.das
        /// c. Projections (Variables) 
        /// d. Selection conditions
        /// </summary>
        public OpenDAPURI()
        {
            this.server = "";
            this.openDAPFile = "";
            this.variables = new List<string>();
            this.variableTypes = new List<DapVariableType>();
            this.internalTypes = new List<InternalDAPType>();
            this.selectionMap = new Dictionary<int, List<Selection>>();
        }

        #region Public Properties

        /// <summary>
        /// Open DAP Server Name from where to read the data.
        /// </summary>
        public string ServerName
        {
            get { return this.server; }
            set { this.server = value; }
        }

        /// <summary>
        /// Name of the data file on the server.
        /// </summary>
        public string OpenDAPFile
        {
            get { return this.openDAPFile; }
            set { this.openDAPFile = value; }
        }

        /// <summary>
        /// The number of variables to be read.
        /// </summary>
        public int VariableCount
        {
            get { return this.variables.Count; }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// This method returns the name of the variable and its type
        /// at the specified index.
        /// </summary>
        public void GetVariableDetails(int index, out string varName, out DapVariableType varType)
        {
            if (index < 0 || (index > this.variables.Count - 1))
                throw new IndexOutOfRangeException();

            varName = this.variables[index];
            varType = this.variableTypes[index];
        }

        /// <summary>
        /// This method returns the tpye of the variable at the index passed.
        /// </summary>
        public DapVariableType GetVariableType(int index)
        {
            if (index < 0 || (index > this.variables.Count - 1))
                throw new IndexOutOfRangeException();

            return this.variableTypes[index];
        }

        /// <summary>
        /// This method returns the InternalDAPType object for the variable 
        /// at the index passed.
        /// </summary>
        public InternalDAPType GetVariableInternalType(int index)
        {
            if (index < 0 || (index > this.variables.Count - 1))
                throw new IndexOutOfRangeException();

            return this.internalTypes[index];
        }

        /// <summary>
        /// Adds a variable to the variable collection storing
        /// the type of the variable  too.
        /// </summary>
        /// <param name="varName"></param>
        /// <param name="type"></param>
        public void AddVariable(string varName, DapVariableType type)
        {
            if (string.IsNullOrEmpty(varName))
                throw new ArgumentException("Variable Name is invalid");

            if (null == this.variables)
            {
                this.variables = new List<string>();
                this.variableTypes = new List<DapVariableType>();
                this.internalTypes = new List<InternalDAPType>();
            }

            this.variables.Insert(this.variables.Count, varName);
            this.variableTypes.Insert(this.variableTypes.Count, type);
            this.selectionMap.Add(this.variables.Count - 1, null);
        }

        public void AddInternalTypeDetails(int index, InternalDAPType internalType)
        {
            if (index < 0 || (index > this.variables.Count - 1))
                throw new IndexOutOfRangeException();

            this.internalTypes.Insert(index, internalType);
        }

        /// <summary>
        /// Gets the index for the variable name passed.
        /// </summary>
        /// <param name="varName"></param>
        /// <returns>Index for the variable if found else -1</returns>
        public int GetVariableIndex(string varName)
        {
            int index = -1;
            int counter = 0;
            foreach (string str in this.variables)
            {
                if (str == varName)
                {
                    index = counter;
                    break;
                }
                ++counter;
            }
            return index;
        }

        /// <summary>
        /// This method adds the selection for the variable index
        /// </summary>
        /// <param name="index"></param>
        /// <param name="sel"></param>
        public void AddSelection(int index, Selection sel)
        {
            if (index < 0 || (index > this.variables.Count - 1))
                throw new IndexOutOfRangeException();

            List<Selection> selections = this.selectionMap[index];
            if (null == selections)
                selections = new List<Selection>();

            selections.Add(sel);
            this.selectionMap[index] = selections;
        }

        /// <summary>
        /// This method adds the selection for the variable index
        /// </summary>
        /// <param name="index"></param>
        /// <param name="sel"></param>
        public List<Selection> GetSelection(int index)
        {
            if (index < 0 || (index > this.variables.Count - 1))
                throw new IndexOutOfRangeException();

            return this.selectionMap[index];
        }

        #endregion

        #region Member Variables

        private string server;

        private string openDAPFile;

        private List<string> variables;

        private List<DapVariableType> variableTypes;

        private List<InternalDAPType> internalTypes;

        private Dictionary<int, List<Selection>> selectionMap;

        #endregion

        private const string Protocol = "http://";
    }
}
